/** Implementation of Scanner Class.
 * @file 
 * @author Jeremy Schultz
 */
#include "Scanner.h"

using namespace std;

//-----------------------------------------------------------------------------
// Constructor
Scanner::Scanner(std::istream* _in, SymbolTable::SymbolTable* _symbolTable, Administration* _admin)	{
	in = _in;
	symbolTable = _symbolTable;
	admin = _admin;

	lineNum = 1;

	lookAhead = false;
	lookAheadToken = NULL;

	eofOccured = false;

	#ifdef TEST
		cout << "Line " << setw(2) << lineNum << ":" << endl;
	#endif
} 

//-----------------------------------------------------------------------------
// Getter - lineNum
int Scanner::LineNum()	{
	return lineNum;
}

//-----------------------------------------------------------------------------
// Skip white space and comments
void Scanner::SkipWhiteSpace()	{
	char c;

	c = in->get();
	while(isspace(c) || c == '$')	{
		if (c == '\n')	{
			lineNum++;
			#ifdef TEST
				cout << "Line " << setw(2) << lineNum << ":" << endl;
			#endif
		}
		if (c == '$')	{
			while(( in->get() ) != '\n');
			lineNum++;
			#ifdef TEST
				cout << "Line " << setw(2) << lineNum << ":" << endl;
			#endif
		}
		c = in->get();
	}
	in->unget();
}

//-----------------------------------------------------------------------------
// Find the Token type
Token::TokenType Scanner::findTokenType()	{
	char c = in->peek();

	// Word
	if (isalpha(c))	{
		return Token::ID;
	}
	
	// Number
	if (isdigit(c))	{
		return Token::NUMBER;
	}

	// Symbol
	in->get();
	switch(c)	{
		case '&': return Token::AND;
		case '(': return Token::LEFT_PARATHESIS;
		case ')': return Token::RIGHT_PARATHESIS;
		case '*': return Token::TIMES;
		case '+': return Token::ADD;
		case ',': return Token::COMMA;
		case '-':
			if (in->peek() == '>')	{
				in->get();
				return Token::ARROW;
			}else	{
				return Token::MINUS;
			}
		case '.': return Token::PERIOD;
		case '/': return Token::DIV;
		case ':':
			if (in->peek() == '=')	{
				in->get();
				return Token::ASSIGN;
			}else	{
				admin->errorBadAssign(lineNum, in->peek() );
			}
		case ';': return Token::SEMI_COLON;
		case '<': return Token::LESS_THAN;
		case '=': return Token::EQUAL;
		case '>': return Token::GREATER_THAN;
		case '[':
			if (in->peek() == ']')	{
				in->get();
				return Token::GUARD_SEPERATOR;
			}else	{
				return Token::LEFT_BRACKET;
			}
		case '\\': return Token::MOD;
		case ']': return Token::RIGHT_BRACKET;
		case '|': return Token::OR;
		case '~': return Token::NOT;
	}

	admin->errorInvalidChar(lineNum, c);

	return Token::ERROR;
}


//-----------------------------------------------------------------------------
// Get the next Token
Token* Scanner::nextToken()	{
	Token::TokenType type;
	char c;
	int num;
	string word;

	// If EOF has alreayd occured then this is an unrecoverable ERROR
	//  This is a hack to avoid a contious loop 
	if (eofOccured) {
		admin->errorEOF(lineNum);
		exit (-3);
	}

	// Reset look ahead token
	if (lookAhead == true)	{
		lookAhead = false;
		return lookAheadToken;
	}

	SkipWhiteSpace();

	if (in->peek() == EOF)	{
		eofOccured = true;
		return new Token;
	}

	type = findTokenType();

	if (type == Token::ERROR)	{
		return nextToken();
	}

	// Numeric
	if (type == Token::NUMBER)	{
		for(num=0,c=in->peek(); isdigit(c); in->get(),c=in->peek() ){
			num = 10 * num + (c - '0');
		}

		// If there is other characters after numbers, give an error
		if (isalpha(c) )	{
			admin->errorBadNumber(lineNum, num, c);
			return new Token(Token::ERROR);
		}
		return new Numeric(num);
	}
	
	// Word or Keyword
	if (type == Token::ID)	{
		for(c=in->peek(); isalnum(c) || c == '_'; in->get(),c=in->peek() ){
			word.push_back(c);
		}

		// Check if we should return word as a keyword
		if (symbolTable->isKeyword(word))	{
			return new Word(word, Token::KEYWORD);
		}

		return new Word(word);

/*
		w = symbolTable->search(word);
		if (w->Type() != Token::EMPTY) {
			cout << "w: " << w->toString() << endl;
		}
		w = symbolTable->insert( new Word(word) );

		return w;
*/

	}

	return new Token(type);
}

//-----------------------------------------------------------------------------
// Get the look ahead Token
Token* Scanner::LookAhead()	{
	if (lookAhead == false)	{
		lookAheadToken = Scanner::nextToken();
		lookAhead = true;
	}
	return lookAheadToken; 
}
